#include <assert.h>
#include <ctype.h>
#include <locale.h>
#include <errno.h>
#include <float.h>
#include <inttypes.h>
#include <limits.h>
#include <math.h>
#include <pthread.h>
#include <setjmp.h>
#include <signal.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdnoreturn.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include<windows.h>

/*
The effective type of a data object is the last type information that was associated with it, if any.
*/
void effective_type(void)
{
    printf("***** effective_type *****\n");
    // a normal variable, effective type uint32_t, and this type never changes
    uint32_t a = 0.0;
    // effective type of *pa is uint32_t, too, simply
    // because *pa is the object a
    uint32_t* pa = &a;
    // the object pointed to by q has no effective type, yet
    void* q = malloc(sizeof(uint32_t));
    // the object pointed to by q still has no effective type,
    // because nobody has written to it
    uint32_t* qb = q;
    // *qb now has effective type uint32_t because a uint32_t value was written
    *qb = 37;
    // the object pointed to by r has no effective type, yet, although
    // it is initialized
    void* r = calloc(1, sizeof(uint32_t));
    // the object pointed to by r still has no effective type,
    // because nobody has written to or read from it
    uint32_t* rc = r;
    // *rc now has effective type uint32_t because a value is read
    // from it with that type. The read operation is valid because we used calloc.
    // Now the object pointed to by r (which is the same as *rc) has
    // gained an effective type, although we didn't change its value.
    uint32_t c = *rc;
    // the object pointed to by s has no effective type, yet.
    void* s = malloc(sizeof(uint32_t));
    // the object pointed to by s now has effective type uint32_t
    // because an uint32_t value is copied into it.
    memcpy(s, r, sizeof(uint32_t));
}

/*
If we have two pointer arguments of the same type, 
the compiler can't make any assumption and will always have
to assume that the change to *e may change *f:
*/
void restrict_qualification_same_type(float *e, float *f)
{
    printf("***** restrict_qualification_same_type *****\n");
    float a = *f;
    *e = 22;
    float b = *f;
    printf("is %g equal to %g?\n", a, b);
    // all goes well and something like 4 equal to 4 is printed?
}

void restrict_qualification_same_pointer(float *e, float *f)
{
    printf("***** restrict_qualification_same_pointer *****\n");
    float a = *f;
    *e = 22;
    float b = *f;
    printf("is %g equal to %g?\n", a, b);
    //  If we pass the same pointer, the program will still do the right thing and print
    // is 4 equal to 22?
}


/*
Then the compiler may always suppose that e and f point to different objects.

This can turn out to be inefficient, if we know by some outside information that e and f will never point to the same
data object. We can reflect that knowledge by adding restrict qualifiers to the pointer parameters:

Then the compiler may always suppose that e and f point to different objects.
*/
void restrict_qualification(float*restrict e, float*restrict f)
{
    printf("***** restrict_qualification *****\n");
    float a = *f;
    *e = 22;
    float b = *f;
    printf("is %g equal to %g?\n", a, b);
}

void main(void)
{
    effective_type();

    float eval = 77;
    float fval = 4;
    restrict_qualification_same_type(&eval, &fval);
    restrict_qualification_same_pointer(&eval, &eval);
    restrict_qualification(&eval, &fval);
    restrict_qualification(&eval, &eval);
    system("pause");
}